﻿using System;
using System.Collections.Generic;
using Crocodile.AbstractLanguage.Coding;
using Crocodile.CsharpLanguage.Tree;
using Crocodile.LangUtils;



namespace Crocodile.CsharpLanguage.Coding
{
    /// <summary>
    /// C# code generator.
    /// </summary>
    /// <stereotype>stateless service</stereotype>
    public class SharpenGenerator
    {

        public void GenerateModule(CsModule module, Action<string,string> applier)
        {
            foreach (var file in module.Files)
                GenerateFile(file, applier);
        }


        private void GenerateFile(CsFile file, Action<string,string> applier)
        {
            CodeFile cof = new CodeFile();

            file.Header.Trim()
                .Split('\n')
                .ProcessAll(cof.Line,
                            startingAction: () => cof.Indent("//  "),
                            finishingAction: () => { cof.Unindent(); cof.EmptyLine(); }
                           );

            file.Usings
                .ProcessAll(usage => cof.Line("using ", usage, ";"),
                            startingAction: cof.EmptyLine,
                            finishingAction: cof.EmptyLine
                           );

            file.Namespaces
                .ProcessAll(ns => GenerateNamespace(ns,cof));

            applier(file.Name, cof.GetText());
        }


        private void GenerateNamespace(CsNamespace ns, CodeFile cof)
        {
            bool inner = ns.Parent is CsNamespace;
            cof.Line("namespace ", inner ? ns.Name : ns.FullName);
            cof.IndentWithBrace();

            ns.Classes
                .ProcessAll(clazz => GenerateClass(clazz, cof),
                            startingAction: cof.EmptyLine,
                            intermediateAction: cof.EmptyLine,
                            finishingAction: cof.EmptyLine);
            
            cof.UnindentWithBrace();
        }


        private void GenerateClass(CsClass clazz, CodeFile cof)
        {
            GenerateAttributes(clazz, cof);

            cof.Line(clazz.ActualVisibility().ToCode(), " ",
                     (clazz.Static ? "static " : ""),
                     (clazz.Abstract ? "abstract " : ""),
                     (clazz.Partial ? "partial " : ""),
                     (clazz.Struct ? "struct " : "class "),
                     clazz.Name,
                     (clazz.BaseClass == null ? "" : " : " + clazz.BaseClass.FullName));

            cof.IndentWithBrace();

            clazz.Declarations
                .ProcessAll(declaration => GenerateDeclaration(declaration, cof),
                            startingAction: cof.EmptyLine,
                            intermediateAction: cof.EmptyLine,
                            finishingAction: cof.EmptyLine);

            cof.UnindentWithBrace();
        }


        private void GenerateDeclaration(CsDeclaration declaration, CodeFile cof)
        {
            declaration.Of<CsClass>(clazz => GenerateClass(clazz, cof));
            declaration.Of<CsField>(field => GenerateField(field, cof));
            declaration.Of<CsProperty>(prop => GenerateProperty(prop, cof));
            declaration.Of<CsRoutine>(ro => GenerateRoutine(ro, cof));
        }


        private void GenerateField(CsField field, CodeFile cof)
        {
            GenerateAttributes(field, cof);
            string[] ie = field.DefaultValue != null
                              ? field.DefaultValue.Text
                              : null;
            int ien = ie == null ? 0 : ie.Length;

            string line =
                field.ActualVisibility().ToCode() +
                (field.Static ? " static" : "") +
                (field.Readonly ? " readonly" : "") +
                ' ' + field.GetTypeDefForGenerating() +
                ' ' + field.Name +
                (ien == 0 ? ";" : ien == 1 ? ie[0] + ';' : "");
            cof.Line(line);
            if (ien >= 2)
            {
                cof.Indent();
                for (int i = 0; i < ien; i++)
                    cof.Line(ie[i] + (i < ien-1 ? "" : ";"));
                cof.Unindent();
            }
        }


        private void GenerateProperty(CsProperty prop, CodeFile cof)
        {
            GenerateAttributes(prop, cof);
            cof.Line(prop.ActualVisibility().ToCode() + ' ' + prop.GetTypeDefForGenerating() + ' ' + prop.Name);
            cof.IndentWithBrace();
            if (prop.Getter)
            {
                cof.Line("get");
                cof.IndentWithBrace();
                GenerateRunContent(prop.Getter.Content, cof);
                cof.UnindentWithBrace();
            }
            if (prop.Setter)
            {
                cof.Line("set");
                cof.IndentWithBrace();
                GenerateRunContent(prop.Setter.Content, cof);
                cof.UnindentWithBrace();
            }
            cof.UnindentWithBrace();
        }


        private void GenerateRoutine(CsRoutine ro, CodeFile cof)
        {
            GenerateAttributes(ro, cof);

            bool hasBody = ro.Content.IsNotEmpty || !ro.Partial && !ro.Abstract;

            string line =
                (ro.Partial ? "partial" : ro.ActualVisibility().ToCode()) +
                (ro.Static ? " static" : "") +
                (ro is CsConstructor ? "" : ' ' + (ro.TypeDef ?? "void")) +
                ' ' + (ro is CsConstructor ? ro.Class.Name : ro.Name) +
                ro.Parameters.JoinAll(p => p.Specification(), "(", ", ", ")", "()") +
                (hasBody ? "" : ";");
            cof.Line(line);

            if (hasBody)
            {
                cof.IndentWithBrace();
                GenerateRunContent(ro.Content,cof);
                cof.UnindentWithBrace();
            }
        }


        private void GenerateRunContent(IList<CsStatement> content, CodeFile cof)
        {
            foreach (var stmt in content)
            {
                foreach (var line in stmt.Text)
                {
                    cof.Line(line);
                }
            }
        }


        private void GenerateAttributes(CsEntity entity, CodeFile cof)
        {
            foreach (var attribute in entity.Attributes)
            {
                cof.Line("[", attribute.AttributeName, "]");
            }
        }

    }
}
